/**
 * \file utf8.c
 * \brief Basic functions for handling UTF-8 encoded text. Note that you can
 * also easily read out Unicode code-points, which are 32-bit unsigned
 * integers.
 **/

#include "utf8.h"

/* Some bit-sequence macros (to make this code more maintainable) */
#define B_00000111 0x7
#define B_00001111 0xF
#define B_00011111 0x1F
#define B_00111111 0x3F
#define B_10000000 0x80
#define B_11000000 0xC0
#define B_11100000 0xE0
#define B_11110000 0xF0
#define B_00000111_11000000 0x7C0
#define B_00001111_11000000 0xFC0
#define B_11110000_00000000 0xF000
#define B_00011100_00000000_00000000 0x1C0000
#define B_00000011_11110000_00000000 0x3F000

int r_utf8_read_uni_char(uint8_t** str, uint8_t* limit, uni_char* dest)
{
  /* TODO: Current implementation seems clear, yet inefficient. Eventually
   * it should be improved. */
  assert(str != NULL); assert((*str) != NULL);

  /* str_t will be used to scan through string, and eventually update str.
   * However, str is only updated if UTF-8 is encoded correctly. */
  uint8_t* str_t = *str;
  /* These will be used to construct parts of dest from the individual
   * octets. */
  uni_char w, x, y, z;
  /* Other 3 octets */
  uint8_t* str_t2 = str_t + 1, *str_t3 = str_t + 2, *str_t4 = str_t + 3;

  /* Few sanity checks: */
  if (str_t >= limit) return R_UTF8_LIMITED;
  if (r_utf8_in_middle(*str_t)) return R_UTF8_SEEK;

  if (r_utf8_is_ascii(*str_t)) {
    /* 1 octet: */
    *dest = (uni_char) (*str_t);
    (*str)++;
  } else if (r_utf8_is_start2(*str_t)) {
    /* 2 octets: 110yyyyy 10zzzzzz => yyyyyzzzzzz*/
    if ((str_t + 1) >= limit) return R_UTF8_LIMITED;
    if (!r_utf8_in_middle(*str_t2)) return R_UTF8_INVALID;
    y = (*str_t) & B_00011111;
    z = (*(str_t2)) & B_00111111;
    *dest = (y << 6) | z;
    (*str) += 2;
  } else if (r_utf8_is_start3(*str_t)) {
    /* 3 octets: 1110xxxx 10yyyyyy 10zzzzzz => xxxxyyyy yyzzzzzz */
    if ((str_t + 2) >= limit) return R_UTF8_LIMITED;
    if (!r_utf8_in_middle(*str_t2) or !r_utf8_in_middle(*str_t3))
      return R_UTF8_INVALID;
    x = (*str_t) & B_00001111;
    y = (*str_t2) & B_00111111;
    z = (*str_t3) & B_00111111;
    *dest = (x << 12) | (y << 6) | z;
    (*str) += 3;
  } else if (r_utf8_is_start4(*str_t)) {
    /* 4 octets: 11110www 10xxxxxx 10yyyyyy 10zzzzzz
     *   => wwwxx xxxxyyyy yyzzzzzz */
    if ((str_t + 3) >= limit) return R_UTF8_LIMITED;
    if (!r_utf8_in_middle(*str_t2) or !r_utf8_in_middle(*str_t3)
        or !r_utf8_in_middle(*str_t4))
      return R_UTF8_INVALID;
    w = (*str_t) & B_00000111;
    x = (*str_t2) & B_00111111;
    y = (*str_t3) & B_00111111;
    z = (*str_t4) & B_00111111;
    *dest = (w << 18) | (x << 12) | (y << 6) | z;
    (*str) += 4;
  } else return R_UTF8_INVALID;
  return R_SUCCESS;
}


int r_utf8_write_uni_char(uint8_t** str, uint8_t* limit, uni_char c)
{
  assert(str != NULL); assert((*str) != NULL);

  if (not r_utf8_valid_uni_char(c)) return R_UTF8_INVALID;
  int bytes = r_utf8_needs(c);
  uint8_t *str_t = *str;
  /* Other 3 octets: */
  uint8_t *str_t2 = str_t + 1, *str_t3 = str_t + 2, *str_t4 = str_t + 3;

  if (str_t + bytes > limit) return R_UTF8_LIMITED;
  if (bytes == 1) {
    *str_t = (uint8_t) c;
    (*str)++;
  } else if (bytes == 2) {
    /* 2 octets: yyy yyzzzzzz => 110yyyyy 10zzzzzz */
    *str_t = ((B_00000111_11000000 & c) >> 6) | B_11000000;
    *str_t2 = (B_00111111 & c) | B_10000000;
    (*str) += 2;
  } else if (bytes == 3) {
    /* 3 octets: xxxxyyyy yyzzzzzz => 1110xxxx 10yyyyyy 10zzzzzz */
    *str_t = ((B_11110000_00000000 & c) >> 12) | B_11100000;
    *str_t2 = ((B_00001111_11000000 & c) >> 6) | B_10000000;
    *str_t3 = (B_00111111 & c) | B_10000000;
    (*str) += 3;
  } else if (bytes == 4) {
    /* 4 octets: 000wwwxx xxxxyyyy yyzzzzzz
     * => 11110www 10xxxxxx 10yyyyyy 10zzzzzz */
    *str_t = ((B_00011100_00000000_00000000 & c) >> 18) | B_11110000;
    *str_t2 = ((B_00000011_11110000_00000000 & c) >> 12) | B_10000000;
    *str_t3 = ((B_00001111_11000000 & c) >> 6) | B_10000000;
    *str_t4 = (B_00111111 & c) | B_10000000;
    (*str) += 4;
  } else assert_never();
  return R_SUCCESS;
}
